#!/bin/bash

# hesschlib - bash/linux utility functions
# Hessel Schut, hessel@isquared.nl

# ---- key:value accessor functions ----------------------------------------- #
# get key or value from key:value variable in $1
function getkey() { echo ${!1/:*}; }
function getval() { echo ${!1/*:}; }

# set key or value to $2 in key:value variable $1
function setval() { eval ${1}=$(getkey ${1}):${2}; }
function setkey() { eval ${1}=${2}:$(getval ${1}); }

function incval() { [ -z "$(getval ${1})" ] && setval ${1} 0 || setval ${1} $(($(getval ${1}) + 1));  }
function decval() { [ -z "$(getval ${1})" ] && setval ${1} 0 || setval ${1} $(($(getval ${1}) - 1));  }

# ---- file:linenumber utilities -------------------------------------------- #
# delete line 'n' in file 'foo' for 'foo:n' argument (pure Bash / sed versions)
# function rmline { linenr=0; _t=${1}; while read line; do if ((++linenr != $(getval _t))); then echo ${line}; fi; done < $(getkey _t); }
function rmline  { _t=${1}; sed -i $(getval _t)d $(getkey _t) ; }

# open file 'foo' in vi on line 'n' for 'foo:n' argument
function viline { _t=${1}; vim $(getkey _t) +$(getval _t); }

# ---- list processing ------------------------------------------------------ #
function car { echo ${1}; }
function cdr { shift; echo ${@}; }

# is this an empty list?
function empty { [ -z "${*}" ]; }

# actually llen() can be implemented as just `function llen() {echo ${#};}', 
# but this is waaaaay cooler... :-)
function llen () { empty ${*} && echo 0 || echo $((1 + $(llen $(cdr ${@})))) ; }

# print truth value of arguments
function bool() { ${@} && echo t || echo f; }

# tail of list (last element)
function ltail() { [ $(llen ${*}) -eq 1 ] && echo ${*} || ltail $(cdr ${*}); }
# not as impressive in a functrace, but a lot more efficient:
function ltail() { [ ${#} -eq 1 ] && echo ${*} || ltail $(cdr ${*}); }
# not as fun as recursion, but constant time. The indirection using $# is cool, though:
function ltail() { echo ${!#}; }

# append item to list
function append() { eval ${1}=\"\$${1} ${2}\"; }

# mutual recursive tests for odd-/ evenness
function iseven() { if [ ${1} -eq 0 ]; then return 0; fi; isodd $((${1} - 1)); } 
function isodd() { if [ ${1} -eq 0 ]; then return 1; fi; iseven $((${1} - 1)); } 
# test if value is numeric
numeric() { [[ "${1}" -eq "${1}" ]] 2>/dev/null; }

# ---- Number conversions ---------------------------------------------- #
# C itoa() implementation in Bash, simplifies conversion from base 10 
function itoa() { res=''; val=${1}; base=${2:-10}; i=${#val}; digit='0123456789abcdef'; for ((; val && i ; --i, val /= base)); do res=${digit:$((val % base)):1}${res}; done; echo ${res};}

# hexdigit to decimal 
function d_hex2dec() { i=0; for j in {{0..9},{a..f}}; do [ "${j}" == "${1}" ] && { echo ${i}; return 0; }; ((i++)); done;  return 1; }
# hexdigit to decimal / should be faster
function d_hex2dec() { case ${1} in a|A) echo 10 ;; b|B) echo 11 ;; c|C) echo 12 ;; d|D) echo 13 ;; e|E) echo 14 ;; f|F) echo 15 ;; [[:digit:]]) echo ${1} ;; esac; }

hex2dec() { _t=0; _h=${1/0x/}; _m=1; for ((i=${#_h} - 1; i >= 0; i--)); do _t=$((_t + _m * $(d_hex2dec ${_h:${i}:1}))); _m=$((_m * 16)); done; echo ${_t}; }

dec2hex() { itoa ${1} 16; }

# test whether variable is defined
function defined() { [[ -n ${!1++} ]]; }

# ---- snmp stuff ----------------------------------------------------------- #
# append MIB to MIBS variable
function pushmib() { MIBS=${MIBS:-+}${MIBS:+:}${1}; }

# remove MIB from MIBS variable or, without argument, pop last MIB from MIBS
# function popmib { [ ! -z "${1}" ] && MIBS=$(sed s/\:*${1}// <<< ${MIBS}) || MIBS=$(sed s/\:[^:]*$// <<< ${MIBS}); }
# pure bash variant:
function popmib() { unset _m; [ ${#} -eq 1 ] && _f=${1} || _f=$(ltail ${MIBS//:/ }); for _mib in ${MIBS//:/ }; do [ "${_mib/+}" == "${_f}" ] || _m="${_m:-+}${_m:+:}${_mib/+}"; done; MIBS=${_m};}

# ---- RIPE whois ----------------------------------------------------------- #
# list routes originating from AS $1
function as-routes { whois -h whois.ripe.net -i origin -T route -K ${1} | awk '/^route/ { print $2}'; }

# list other objects with the same manintainer as $1, optionally filter on object 
# type $2
function same-mntnr { whois -h whois.ripe.net -i mnt-by -K $(whois ${1} | awk '/^mnt-by/ { print $2; exit;}') | { [ ! -z "${2}" ] && awk "/^${2}/"'{ print $2 }' || cat ;}; }

# provide whois history logging
# function hwhois() { whois ${*} | tee ${HOME}/whois_hist/$(ltail ${*})-$(date +%F-%T).txt; }

# ---- MAC addresses -------------------------------------------------------- #
# canonicalize MAC addresses (does grok most forms)
function maccanon { _c=''; for _o in ${1//[\.:-]/ }; do [ ${#_o} -eq 4 ] && _o="${_o::2} ${_o:2:2}" ; [ ${#_o} -eq 1 ] && _o="0${_o}"; _c=${_c}${_c:+ }${_o}; done; echo ${_c// /-}; }
# function maccanon { _c=${1//[\.: -]/}; echo -n ${_c::2}; for n in {1..5}; do echo -n -${_c:$((2*n)):2};done;}

# truncate MAC to OUI
function mac2oui { _c=$(maccanon ${1}); tr 'a-f' 'A-F' <<< ${_c::8}; }

# grep oui.txt for MAC or OUI, accepts OUI path a second argument (defaults to ./oui.txt )
function mac2vendor { grep -e "^$(mac2oui ${1})" ${2:-oui.txt} | while read _v; do echo ${_v#*)}; done; }

# get arp table and print hardware addresses with their vendor (BUG: doesn't like BSD)
function arpinquisition { arp -n | awk '!/incomplete/ && NR > 1 { print $3}' | while read hwaddr; do echo -n "${hwaddr}  "; mac2vendor $hwaddr; done; }

# hosthost - feed addresses from host(1) back to host
function hh() { host ${1} | while read line ; do echo ${line}; hh=$([[ "${line}" =~  "has address" ]] && host $(lambda 'eval echo \$${#}' ${line})); [[ -z "${hh}" ]] || echo ${hh}; done; }

# ---- Processes ------------------------------------------------------------ #
# grep processes for pattern
function psgrep { ps -ef | awk '/'${1}'/ && !/awk \/'${1}'\// { print $2 }'; }

# ---- Date/Time voodoo ----------------------------------------------------- #
# Even more voodoo due to darwins date suckiness
function unepoch { for time_t in ${*}; do [ "${MACHTYPE/*darwin*/t}" == "t" ] && date -j -f %s ${time_t} || date -d @${time_t}; done; }
function epoch2str { unepoch ${@}; }
function str2epoch { for hr_time in ${*}; do [ "${MACHTYPE/*darwin*/t}" == "t" ] && echo "Darwins date sucks too much." || date -d "${hr_time}" +%s; done; }

# ---- Shell Functions ------------------------------------------------------ #
# get function definitions
function lsfuncdefs() { for function in ${*}; do set | sed -n '/^'${function}'/, /^}$/p'; done; }

# get variable definitions
function getvar() { declare 2>/dev/null | while read def; do [[ "${def}" =~ ^"${1}" ]] && { echo ${def}; exit;}; done; }

# test if variable contains an array
function isarray() { [[ "$(getvar ${1})" =~ "${1}"'=(' ]]; }

# set IFS, maintaining previous value in _IFS_T
function set_ifs() { _IFS_T="${IFS}"; IFS="${1}"; }

# restore IFS to value before calling set_ifs()
function restore_ifs() { IFS="${_IFS_T}"; unset _IFS_T; }

# map function to each input line in pipe
function map() { empty "${*}" && cat || { while read line; do eval "${*} \"${line}\""; done; }; }

# lambda 'anonymous function' arg1 ... argn
function lambda() { _f=${1}; shift; function _l { eval "${_f}"; }; _l ${*} ; unset _l; }
alias λ=lambda

# poor man's awk, print line if field matches RE: 
#  match_field <field> <pattern> <line>
function match_field() { NF=${1}; RE=${2}; shift 2; [[ ${!NF} =~ ${RE} ]] && echo ${*}; }

# poor man's cut, extract field from line: extract_field <field> <line>
function extract_field() { NF=${1}; shift ; echo ${!NF}; }

# push and pop for bash arrays
function push() { eval ${1}[$(($(eval echo \${#${1}[*]}) + 1))]=$2; }

# match field in line: match_field <fieldnum> <RE> <field> [field ...]
function match_field() { nf=${1}; re=${2}; shift 2; [[ ${!nf} =~ ${re} ]] && echo ${*}; }
# extract field from line: extract_field <fieldnum> <field> [field ...]
function extract_field() { nf=${1}; shift; echo ${!nf}; } 

# ---- Math Functions ------------------------------------------------------- #
# absolute value
function abs() { [[ ${1} -ge 0 ]] && echo ${1} || echo $((0 - ${1})); }

# wraparound: wrap <value> <max> [min]
function wrap() { echo $((${3:-0} + $(abs $((${1} - ${3:-0}))) % (${2} - ${3:-0}))); }

# ---- Terminal control ---------------------------------------------------- #
# get terminal geometry
function term_size() { { read -p $'\e[18t' -s -r -d t s; } <> /dev/tty; echo ${s#*;}; }
# get terminal columns
function term_cols() { echo ${COLUMNS:-$(s=$(term_size)  && s=${s#*;} && echo ${s/*;})}; }
# get terminal lines
function term_lines() { echo ${LINES:-$(s=$(term_size) && s=${s#*;} && echo ${s/;*})}; }
function term_rows() { term_lines; }
